package org.keyintegrity.webbeans;

import java.util.List;

import org.apache.log4j.Logger;
import org.apache.tapestry.BaseComponent;
import org.apache.tapestry.IMarkupWriter;
import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.components.IPrimaryKeyConverter;
import org.keyintegrity.webbeans.metadata.FieldMetadata;
import org.keyintegrity.webbeans.metadata.MetadataManager;
import org.keyintegrity.webbeans.utils.BeanUtils;

public abstract class BeanEditorTypeMappings extends BaseComponent {

    private static final Logger logger = Logger.getLogger(BeanEditorTypeMappings.class);
    
    public abstract BeanEditor getBeanEditor();
    public abstract void setBeanEditor(BeanEditor beanEditor);
    public abstract int getListEntryIndex();

    public abstract void setRemovedIndex(int index);
    public abstract int getRemovedIndex();
    
    public abstract void setModifiedList(List list);
    public abstract List getModifiedList();
    
    public abstract String getNewEntryType();
    public abstract void setNewEntryType(String value);
    
    public abstract Object getNewEntryInstance();
    public abstract void setNewEntryInstance(Object obj);
    
    public abstract void setNewEntryInstanceCreated(boolean b);
    public abstract boolean isNewEntryInstanceCreated();
    
    private Object getBean()          { return getBeanEditor().getBean(); }
    private String getBeanFieldName() { return getBeanEditor().getBeanFieldName(); }

    public BeanEditorTypeMappings getInstance() { return this; }

    @Override
    @SuppressWarnings("unchecked")
    protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle) {
       if (logger.isDebugEnabled()) {
            logger.debug("renderComponent()");
            logger.debug("isRewinding    = " + cycle.isRewinding());
            logger.debug("bean           = " + getBean());
            logger.debug("bean.fieldName = " + getBeanFieldName());
        }
        
        super.renderComponent(writer, cycle);

        if (cycle.isRewinding() && getModifiedList() != null) {
            if (isNewEntryInstanceCreated()) {
                getModifiedList().add(getNewEntryInstance());
                setNewEntryInstanceCreated(false);
                setNewEntryInstance(null);
            } else if (getRemovedIndex() != -1) {
                getModifiedList().remove(getRemovedIndex());
                setRemovedIndex(-1);
            }
            setModifiedList(null);
        }
    }

    private boolean isXsltBeanEditor() {
        return getBeanEditor() instanceof XsltBeanEditor;
    }

    public String getBrTag() {
        return isXsltBeanEditor() ? null : "br";
    }
    
    public String getRemoveTagWrapper() {
        return isXsltBeanEditor() ? "removeEntryAction" : null;
    }
    
    public String getAddTagWrapper() {
        return isXsltBeanEditor() ? "addEntryAction" : null;
    }
    
    public String getListEntryTagWrapper() {
        return isXsltBeanEditor() ? "listEntry" : null;
    }

    public Object getComplexTypeListEntryValue() {
        return getListValue().get(getListEntryIndex());
    }

    public List getListValue() {
        return (List) BeanUtils.getBeanFieldValue(getBean(), getBeanFieldName());
    }
    
    Object getValue() {
        if (! isSimpleType()) {
            return ((List)BeanUtils.getBeanFieldValue(getBean(), getBeanFieldName()))
                .get(getListEntryIndex());
        }
        return BeanUtils.getBeanFieldValue(getBean(), getBeanFieldName());
    }
    
    @SuppressWarnings("unchecked")
    void setValue(Object value) {
        Class clazz = getBeanFieldType();
        
        if (! isSimpleType()) {
            //  TODO Type safety (Use metadata)
            value = value == null ? "" : value;
            
            List list = getListValue();
            int index = getListEntryIndex();
            if (index < list.size()) {
                //  TODO Type safety
                list.set(index, value);
            } else {
                //  Ignore binding removed entry            
            }
        } else {
            BeanUtils.setBeanFieldValue(getBean(), getBeanFieldName(), value, clazz);
        }
    }
    
    public Class getBeanFieldType() {
        return BeanUtils.getBeanFieldType(getBean().getClass(), getBeanFieldName());
    }
    
    public IPrimaryKeyConverter getListConverter() {
        return new ListConverter(this);
    }
    
    public boolean isSimpleTypeListEntry() {
        return BeanUtils.isSimpleType(getListEntryType());
    }
    
    private Class<?> getListEntryType() {
        return getValue().getClass();
    }
    
    /**
     * @return true if BeanEditor currently iterates over simple type bean field
     */
    public boolean isSimpleType() {
        return BeanUtils.isSimpleType(getBeanFieldType());
    }
    
    //    TODO More Types (see JAXB specification as an example of type mappings)
    
    public boolean isList() {
        return BeanUtils.isList(getBeanFieldType());
    }
    
    public Object getBeanFieldValue() {
        return BeanUtils.getBeanFieldValue(getBean(), getBeanFieldName());
    }
    
    public FieldMetadata getBeanFieldMetadata() {
        return MetadataManager.getFieldMetadata(getBean().getClass(), getBeanFieldName());
    }
    
    public boolean isReadOnly() {
        return getBeanEditor().isReadOnly() || getBeanFieldMetadata().isReadOnly();
    }

    @SuppressWarnings("unchecked")
    public void addListEntry() {
        if (logger.isDebugEnabled()) {
            logger.debug("addListEntry()");
        }
        try {
            Object newEntry = createNewEntryInstance();
            setModifiedList(getListValue());
            setNewEntryInstance(newEntry);
            setNewEntryInstanceCreated(true);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }
    
    private Object createNewEntryInstance() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        Object newEntry;
        //  TODO Use metadata for default newEntryType value
        String newEntryType = getNewEntryType();
        if (newEntryType != null) {
            if (logger.isDebugEnabled()) {
                logger.debug("Trying to create new instance of " + newEntryType);
            }
            newEntry = Class.forName(newEntryType).newInstance();
        } else {
            newEntry = "New String Entry";
        }
        return newEntry;
    }
    
    public void removeListEntry() {
        if (logger.isDebugEnabled()) {
            logger.debug("removeListEntry()");
        }
        setModifiedList(getListValue());
        setRemovedIndex(getListEntryIndex());
    }
}
